"""
====================================================================
Copyright (C) 2012-2013 Eric Blond

This file is part of Neptune.

Neptune is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

Neptune is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Neptune.  If not, see <http://www.gnu.org/licenses/>.
====================================================================

Created on Jun 16, 2012

@author: Eric
"""

import unittest
import logging
import inspect
import threading

#######

LOGGER = logging.getLogger('neptune.nucleus')

#######

class TestListener(object):
    
    def __init__(self):
        self.history = []

    def __call__(self, old_value, new_value):
        if isinstance(old_value, Exception):
            old_value = old_value.__class__
        if isinstance(new_value, Exception):
            new_value = new_value.__class__
        if self.history:
            assert old_value == self.history[-1]
        self.history.append(new_value)

#######

class TestCaseWithLogging(unittest.TestCase):

    def setUp(self):
        logging.basicConfig()
        logging.getLogger().setLevel(logging.INFO)

#######

_LOCK = threading.Lock()

def singleton(function):
    function._NEPTUNE_cache = {}
    
    def internal(*args, **kwargs):
        with _LOCK:
            key = (tuple(args), tuple(kwargs.items()))
            try:
                return function._NEPTUNE_cache[key]
            except KeyError:
                value = function(*args, **kwargs)
                function._NEPTUNE_cache[key] = value
                return value
    
    return internal

#######

def couples(sequence):
    if not isinstance(sequence, (tuple, list)):
        sequence = tuple(sequence)
    len_seq = len(sequence)
    for (i, e1) in enumerate(sequence):
        for i2 in xrange(i + 1, len_seq):
            yield (e1, sequence[i2])

#######

def merge_sets_sequence(sets, element):
    for i in xrange(len(sets)):
        if sets[i] & element:
            return merge_sets_sequence(sets[:i] + sets[i + 1:], sets[i] | element)
    return sets + [element]

#######

def arguments(function):
    assert callable(function)
    try:
        argspec = inspect.getargspec(function)
        if inspect.ismethod(function):
            return argspec.args[1:]
        else:
            return argspec.args
    except TypeError:
        argspec = inspect.getargspec(function.__call__)
        return argspec.args[1:]

#######

class ConstantFunction(object):
    
    def __init__(self, value):
        self._value = value

    def __call__(self, *args, **kwargs):
        return self._value
